From: Florian Müllner Date: Sun, 24 Jul 2022 17:29:50 +0000 (+0200) Subject: quickSettings: Introduce QuickSettingsMenu X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~461^2^2~192 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=47cb354e6c55fb29b1e8466fafb57a6ccc5acb69;p=gnome-shell.git quickSettings: Introduce QuickSettingsMenu The quick settings menu is a popover that arranges items in a reflowing, homogeneous grid. Grid children may span multiple columns, but not rows. For now the QuickSettingsMenu that contains the grid is just a convenience wrapper around the layout manager that does the heavy lifting. The two will become more intertwined when we add support for menu toggles though, so the custom menu type is unfortunately needed. Part-of: --- diff --git a/data/theme/gnome-shell-sass/_widgets.scss b/data/theme/gnome-shell-sass/_widgets.scss index 4ed082f1f..875baf582 100644 --- a/data/theme/gnome-shell-sass/_widgets.scss +++ b/data/theme/gnome-shell-sass/_widgets.scss @@ -33,6 +33,7 @@ // Panel @import 'widgets/panel'; @import 'widgets/corner-ripple'; +@import 'widgets/quick-settings'; // Overview @import 'widgets/overview'; @import 'widgets/window-picker'; diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss new file mode 100644 index 000000000..66a0ed308 --- /dev/null +++ b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss @@ -0,0 +1,8 @@ +.quick-settings { + padding: 4 * $base_padding; +} + +.quick-settings-grid { + spacing-rows: 3 * $base_padding; + spacing-columns: 2 * $base_padding; +} diff --git a/data/theme/meson.build b/data/theme/meson.build index 26471b679..fbf2938b8 100644 --- a/data/theme/meson.build +++ b/data/theme/meson.build @@ -29,6 +29,7 @@ theme_sources = files([ 'gnome-shell-sass/widgets/_overview.scss', 'gnome-shell-sass/widgets/_panel.scss', 'gnome-shell-sass/widgets/_popovers.scss', + 'gnome-shell-sass/widgets/_quick-settings.scss', 'gnome-shell-sass/widgets/_screen-shield.scss', 'gnome-shell-sass/widgets/_screenshot.scss', 'gnome-shell-sass/widgets/_scrollbars.scss', diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 642ad05c3..87adfd140 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -93,6 +93,7 @@ ui/pointerA11yTimeout.js ui/pointerWatcher.js ui/popupMenu.js + ui/quickSettings.js ui/remoteSearch.js ui/ripples.js ui/runDialog.js diff --git a/js/ui/quickSettings.js b/js/ui/quickSettings.js new file mode 100644 index 000000000..82a235850 --- /dev/null +++ b/js/ui/quickSettings.js @@ -0,0 +1,207 @@ +/* exported QuickSettingsMenu */ +const {Clutter, GLib, GObject, St} = imports.gi; + +const Main = imports.ui.main; +const PopupMenu = imports.ui.popupMenu; + +const QuickSettingsLayoutMeta = GObject.registerClass({ + Properties: { + 'column-span': GObject.ParamSpec.int( + 'column-span', '', '', + GObject.ParamFlags.READWRITE, + 1, GLib.MAXINT32, 1), + }, +}, class QuickSettingsLayoutMeta extends Clutter.LayoutMeta {}); + +const QuickSettingsLayout = GObject.registerClass({ + Properties: { + 'row-spacing': GObject.ParamSpec.int( + 'row-spacing', 'row-spacing', 'row-spacing', + GObject.ParamFlags.READWRITE, + 0, GLib.MAXINT32, 0), + 'column-spacing': GObject.ParamSpec.int( + 'column-spacing', 'column-spacing', 'column-spacing', + GObject.ParamFlags.READWRITE, + 0, GLib.MAXINT32, 0), + 'n-columns': GObject.ParamSpec.int( + 'n-columns', 'n-columns', 'n-columns', + GObject.ParamFlags.READWRITE, + 1, GLib.MAXINT32, 1), + }, +}, class QuickSettingsLayout extends Clutter.LayoutManager { + _containerStyleChanged() { + const node = this._container.get_theme_node(); + + let changed = false; + let found, length; + [found, length] = node.lookup_length('spacing-rows', false); + changed ||= found; + if (found) + this.rowSpacing = length; + + [found, length] = node.lookup_length('spacing-columns', false); + changed ||= found; + if (found) + this.columnSpacing = length; + + if (changed) + this.layout_changed(); + } + + _getColSpan(container, child) { + const {columnSpan} = this.get_child_meta(container, child); + return Math.clamp(columnSpan, 1, this.nColumns); + } + + _getMaxChildWidth(container) { + let [minWidth, natWidth] = [0, 0]; + + for (const child of container) { + const [childMin, childNat] = child.get_preferred_width(-1); + const colSpan = this._getColSpan(container, child); + minWidth = Math.max(minWidth, childMin / colSpan); + natWidth = Math.max(natWidth, childNat / colSpan); + } + + return [minWidth, natWidth]; + } + + _getRows(container) { + const rows = []; + let lineIndex = 0; + let curRow; + + /** private */ + function appendRow() { + curRow = []; + rows.push(curRow); + lineIndex = 0; + } + + for (const child of container) { + if (!child.visible) + continue; + + if (lineIndex === 0) + appendRow(); + + const colSpan = this._getColSpan(container, child); + const fitsRow = lineIndex + colSpan <= this.nColumns; + + if (!fitsRow) + appendRow(); + + curRow.push(child); + lineIndex = (lineIndex + colSpan) % this.nColumns; + } + + return rows; + } + + _getRowHeight(children) { + let [minHeight, natHeight] = [0, 0]; + + children.forEach(child => { + const [childMin, childNat] = child.get_preferred_height(-1); + minHeight = Math.max(minHeight, childMin); + natHeight = Math.max(natHeight, childNat); + }); + + return [minHeight, natHeight]; + } + + vfunc_get_child_meta_type() { + return QuickSettingsLayoutMeta.$gtype; + } + + vfunc_set_container(container) { + this._container?.disconnectObject(this); + + this._container = container; + + this._container?.connectObject('style-changed', + () => this._containerStyleChanged(), this); + } + + vfunc_get_preferred_width(container, _forHeight) { + const [childMin, childNat] = this._getMaxChildWidth(container); + const spacing = (this.nColumns - 1) * this.column_spacing; + return [this.nColumns * childMin + spacing, this.nColumns * childNat + spacing]; + } + + vfunc_get_preferred_height(container, _forWidth) { + const rows = this._getRows(container); + + let [minHeight, natHeight] = [0, 0]; + + const spacing = (rows.length - 1) * this.row_spacing; + minHeight += spacing; + natHeight += spacing; + + rows.forEach(row => { + const [rowMin, rowNat] = this._getRowHeight(row); + minHeight += rowMin; + natHeight += rowNat; + }); + + return [minHeight, natHeight]; + } + + vfunc_allocate(container, box) { + const rows = this._getRows(container); + + const availWidth = box.get_width() - (this.nColumns - 1) * this.row_spacing; + const childWidth = availWidth / this.nColumns; + + const isRtl = container.text_direction === Clutter.TextDirection.RTL; + + const childBox = new Clutter.ActorBox(); + let y = box.y1; + rows.forEach(row => { + const [, rowNat] = this._getRowHeight(row); + + let lineIndex = 0; + row.forEach(child => { + const colSpan = this._getColSpan(container, child); + const width = + childWidth * colSpan + this.column_spacing * (colSpan - 1); + let x = box.x1 + lineIndex * (childWidth + this.column_spacing); + if (isRtl) + x = box.x2 - width - x; + + childBox.set_origin(x, y); + childBox.set_size(width, rowNat); + child.allocate(childBox); + + lineIndex = (lineIndex + colSpan) % this.nColumns; + }); + + y += rowNat + this.row_spacing; + }); + } +}); + +var QuickSettingsMenu = class extends PopupMenu.PopupMenu { + constructor(sourceActor, nColumns = 1) { + super(sourceActor, 0, St.Side.TOP); + + Main.layoutManager.connectObject('system-modal-opened', + () => this.close(), this); + + this.box.add_style_class_name('quick-settings'); + + this._grid = new St.Widget({ + style_class: 'quick-settings-grid', + layout_manager: new QuickSettingsLayout({ + nColumns, + }), + }); + this.box.add_child(this._grid); + } + + addItem(item, colSpan = 1) { + this._grid.add_child(item); + this._grid.layout_manager.child_set_property( + this._grid, item, 'column-span', colSpan); + } +};